package com.cskaoyan.javase.oop2._1extends._7initialization._0introduction;

/**
 * 子类对象初始化问题
 * 1.何为子类对象初始化问题？
 * 复习一下我们学习引用数据类型的过程：
 *      a.我们通过学习数组,知道引用数据类型要分成两个部分:
 *          1.栈上存储引用
 *          2.堆上存储对象
 *      对象是实质,但是我们只能够通过引用间接的访问堆上的对象
 *      b.我们通过学习对象与类,完成了引用数据类型的数据类型概念的统一:
 *          1.数据类型: 一组数据和基于数据的合法操作的集合
 *          2.引用数据类型(类): 成员变量和成员方法的集合
 *          所以引用数据类型是特殊的自定义数据类型,这种自定义的数据类型不像基本数据类型,JVM一开始不知道类型信息
 *          所以JVM需要通过类加载了解类型信息
 *          类加载是懒加载,类加载的目的让JVM了解类型信息,从而完成创建对象或者其它类型相关操作
 *          类加载的时机:
 *              1.启动main
 *              2.new对象
 *              3.访问静态成员
 *      c.今天我们学习了继承,我们知道任何类都有父类,子类会继承父类中的所有成员,那么:
 *          这些成员从何而来?
 *          父类中的成员变量如果需要赋值,赋值的顺序是什么样的?赋值的结果是什么呢?为什么是这样呢?
 *          等等
 *          这些问题,统称为子类对象的初始化问题.
 *          即研究子类对象创建时，父类的成员从何而来，父类成员变量的赋值等相关的问题。
 *
 *
 * 我们通过一个案例来研究该问题:
 * 1. 创建一个Person类，有name属性和eat方法
 * 2. 创建一个Star类，有stageName属性和sing方法
 * 3. Star类要继承Person类
 * 4. 创建Star类对象，画出这个过程的内存图，描述子类对象初始化的过程。
 *
 * 思考以下问题：
 *
 * 1.创建Star对象，需要触发类加载，但是Star还有父类，那么需要类加载父类吗？
 * 如果需要，顺序是什么样的？
 * 是需要类加载父类的，并且类加载的顺序是“先父后子”的。
 * 所以这就是类加载的时机的第四种：
 *      触发子类类加载，先类加载它的父类，并且会从最顶层的父类开始，向下完成类加载
 *  Java中最顶层的父类是Object，但是它的类加载对程序运行没有影响，无需考虑。
 *
 * 2.类加载结束后，开始创建对象，堆上有几个对象？存不存在父类对象？
 * 只会创建一个子类对象，不会创建任何父类对象
 *
 * 3.既然只有一个子类对象，那么父类中的成员存在哪里？
 * 就存在这个子类对象当中，在Java中，子类对象中会专门开辟一片空间用来存储它的直接父类的成员
 * 这片区域中会直接存储父类的成员变量（不管访问权限）
 * 这片区域非常像一个父类对象，所以它可以近似的看成一个父类对象，当作父类对象去使用
 * 这片区域在Java中super关键字指向它，所以在Java中：
 * this关键字指向当前对象
 * super关键字指向当前对象中，直接父类的那片区域。这片区域近似的看成父类对象，也确实可以近似的当成父类对象去使用，但super不是指向对象
 *
 *
 * 4.父类成员变量和子类成员变量的赋值顺序和赋值结果是什么呢？
 * 首先，强调： 默认初始化是永远的第一步，JVM在创建对象这个内存结构时，就会给成员变量赋默认值，所以不讨论默认初始化。
 * 除开默认初始化，还有显式赋值，构造代码块赋值，构造器赋值。
 * 它们的赋值顺序也是先父后子的，先完成父类中成员变量的赋值，再进行子类中成员变量的赋值。
 *
 * 5.为什么？为什么赋值的顺序是“先父后子”的？怎么保证赋值一定是先父后子的？
 * 思考：
 * 如果父类的构造器先于子类执行，那么父类中所有成员变量赋值方式都是先于子类的。
 * 这样就能够保证“先父后子”了。
 *
 * 通过构造代码块测试，我们发现构造器的执行确实是先父后子的。
 *
 * 5.为什么？为什么构造器的执行是“先父后子”的？
 *
 * 子类对象的隐式初始化:
 * 在Java的某个类的构造器中,如果第一行没有用this或者super明确的表示去调用某个构造器的话,就永远隐含一句super();
 * 表示调用父类的无参构造器.
 * 这种通过隐含super();去实现构造器执行的先父后子的方式,称之为----子类对象的隐式初始化
 *
 *
 * 子类对象的显式初始化：
 *  调用父类无参构造——子类对象隐式初始化无法给父类成员变量赋值，
 *             想要给父类成员变量赋值，需要使用父类有参构造方法
 *  语法：
 *                 在子类构造器的第一行写:
 *                 super(实参列表);
 *  表示去调用父类的有参构造器。
 *  这种方式就是显式的子类对象初始化，可以给父类成员变量赋值。
 *
 * @since 11:16
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // Star s = new Star();
        // System.out.println(s.stageName);
        Star s = new Star("李四");
        System.out.println(s.stageName);
    }
}

class Person {
    // static {
    //     System.out.println("Person类类加载");
    // }
    String name = "张三";
    // {
    //     System.out.println("Person的构造代码块");
    // }

    public void eat() {
        System.out.println("吃饭多是一件美事!");
    }

    public Person() {
        // super();
    }

    public Person(String name) {
        this.name = name;
    }
}

class Star extends Person {
    // static {
    //     System.out.println("Star类类加载");
    // }
    String stageName = name + "的爸爸";
    // {
    //     System.out.println("Star的构造代码块");
    // }

    public void sing() {
        System.out.println("爱的供养");
    }

    public Star() {
        /*
            假设在这里会去调用父类构造器
            就符合构造器的执行"先父后子"了
            this(参数)表示调用自身构造器
            super(参数)就表示调用父类构造器

            这里只要隐含
            super();
            这一条语句,就可以去调用父类的无参构造方法了
            这种通过隐含super();去实现构造器执行的先父后子的方式,称之为----子类对象的隐式初始化

            在Java的某个类的构造器中,如果第一行没有用this或者super明确的表示去调用某个构造器的话
            就永远隐含一句super();
            表示调用父类的无参构造器.

            调用父类无参构造——子类对象隐式初始化无法给父类成员变量赋值，
            想要给父类成员变量赋值，需要使用父类有参构造方法
            语法：
                在子类构造器的第一行写:
                super(实参列表);
                表示去调用父类的有参构造器。
            这种方式就是显式的子类对象初始化，可以给父类成员变量赋值。
         */
        // 隐含super();
    }

    public Star(String name){
        // 子类对象显式初始化，调用父类有参构造
        // 给父类成员name赋值
        super(name);
    }
}
